package block

import (
	"bytes"
	"crypto/sha256"
	"errors"
	"github.com/corgi-kx/blockchain_golang/util"
	log "github.com/corgi-kx/logcustom"
	"math/big"
	"time"
)

//工作量证明(pow)结构体
type proofOfWork struct {
	*Block
	//难度
	Target *big.Int
}

// TODO 获取POW实例
func NewProofOfWork(block *Block) *proofOfWork {
	var TimeSpace int64 = int64(MineTimeSpace) //预期的区块间隔时间(ns)
	var ChangeT int64 = 4                      //几个区块进行一次难度调整
	//TODO 根据当前区块的高度判断难度
	target := big.NewInt(1)
	//返回一个大数(1 << 256-TargetBits)
	//区块的高度处于第一个不需要调整的区间
	if block.BBlockHeader.Height < int(ChangeT) {
		target.Lsh(target, 256-TargetBits)
		DifficultyInt = target
		//ChangeT的整数倍进行难度的调整
	} else if block.BBlockHeader.Height%int(ChangeT) == 0 {
		b := NewBlockchain()
		var deltaTime int64 = b.GetOffsetBlockTime(block.BBlockHeader.Height-int(ChangeT)+1, block.BBlockHeader.Height-1)
		x := big.NewInt(deltaTime)
		y := big.NewInt((ChangeT - 2) * TimeSpace)
		DifficultyInt.Mul(DifficultyInt, x)
		DifficultyInt.Div(DifficultyInt, y)
	} else {
		b := NewBlockchain()
		DifficultyInt = target.SetBytes(b.GetLastBlockDifficulty())
	}
	targetBytes := DifficultyInt.Bytes()
	//TODO 设置当前block的难度
	block.BBlockHeader.Difficulty = targetBytes
	//将block中的字节转为bigInt
	//target = target.SetBytes(targetBytes)
	pow := &proofOfWork{block, DifficultyInt}
	return pow
}

// 接收区块头时构造的PoW
// 不需要判断难度值
// 根据区块头信息构造即可
func NewProofOfWorkByHeader(block *Block) *proofOfWork {
	target := big.NewInt(1)
	target.SetBytes(block.BBlockHeader.Difficulty)
	pow := &proofOfWork{block, target}
	return pow
}

//用户提交的区块会进入该通道，只接受第一个合法区块
var MinedChan = make(chan *BlockHeader, 1)

//进行hash运算,获取到当前区块的hash值
func (p *proofOfWork) run(wsend WebsocketSender) (*BlockHeader, error) {
	//MinedChain =
	if len(MinedChan) == 1 {
		<-MinedChan
	}
	//这里可以定时发送，每个新连接的用户都有机会挖矿
	var MineReturnBlockHeader *BlockHeader

	wsend.SendBlockHeaderToUser(p.BBlockHeader)
	var breakChan = make(chan bool, 1)
	//如果在第一个区块间隔内还没有挖出块（所有节点），则重新发送区块头
	go func() {
		for {
			select {
			case <-time.After(time.Duration(MineTimeSpace) * time.Nanosecond):
				wsend.SendBlockHeaderToUser(p.BBlockHeader)
			case <-breakChan:
				goto breakPoint
			}
		}
	breakPoint:
	}()

	//有节点挖到了区块
	MineReturnBlockHeader = <-MinedChan
	breakChan <- true

	//其他云计算节点已经挖到区块了
	if MineReturnBlockHeader == nil {
		return nil, errors.New("其他云计算节点已经挖到区块了")
	}
	//返回应该需要返回一个随机数、一个幻方、区块hash、用户交易
	return MineReturnBlockHeader, nil
}

//检验区块是否有效
func (p *proofOfWork) Verify() bool {
	//TODO 验证MerkelRootWHash
	ts := p.BBlockHeader.TransactionToUser
	merkelRootWHash := sha256.Sum256(bytes.Join([][]byte{ts.getTransBytes(), p.BBlockHeader.MerkelRootHash}, []byte("")))
	//此处100 为W重
	for i := 0; i < WNum; i++ {
		merkelRootWHash = sha256.Sum256(merkelRootWHash[:])
	}
	var m1 big.Int
	var m2 big.Int
	m1.SetBytes(merkelRootWHash[:])
	m2.SetBytes(p.BBlockHeader.MerkelRootWHash[:])
	if m1.Cmp(&m2) != 0 {
		log.Debug(m1.String())
		log.Debug(m2.String())
		return false
	}
	//TODO 验证RandomMatrix是否符合要求

	//TODO 验证用户奖励金额
	if p.BBlockHeader.TransactionToUser.Vout[0].Value != TokenRewardNum {
		return false
	}

	//提取区块的难度
	target := big.NewInt(1)
	//target.Lsh(target, 256-TargetBits)
	target.SetBytes(p.BBlockHeader.Difficulty)

	//组合区块的数据
	data := p.jointData(p.BBlockHeader.RandomMatrix)
	hash := sha256.Sum256(data)
	var hashInt big.Int
	hashInt.SetBytes(hash[:])
	if hashInt.Cmp(target) == -1 {
		return true
	}
	return false
}

// TODO 将 上一区块hash、数据、时间戳、难度位数、随机数 拼接成字节数组
func (p *proofOfWork) jointData(randomMatrix RandomMatrix) (data []byte) {
	preHash := p.BBlockHeader.PreHash
	preRandomMatrixByte := RandomMatrixToBytes(p.BBlockHeader.PreRandomMatrix)
	merkelRootHash := generateMerkelRoot(p.Transactions) //p.BBlockHeader.MerkelRootHash
	merkelRootWHash := p.BBlockHeader.MerkelRootWHash
	merkelRootWSignature := p.BBlockHeader.MerkelRootWSignature
	cAByte := CAToBytes(p.BBlockHeader.CA)

	transactionToUserByte := p.BBlockHeader.TransactionToUser.getTransBytes()

	difficultyByte := p.BBlockHeader.Difficulty
	timeStampByte := util.Int64ToBytes(p.BBlockHeader.TimeStamp)
	heightByte := util.Int64ToBytes(int64(p.BBlockHeader.Height))
	randomMatrixByte := RandomMatrixToBytes(randomMatrix)
	//targetBitsByte := util.Int64ToBytes(int64(TargetBits))

	data = bytes.Join([][]byte{
		preHash,
		preRandomMatrixByte,
		merkelRootHash,
		merkelRootWHash,
		merkelRootWSignature,
		cAByte,
		transactionToUserByte,
		difficultyByte,
		timeStampByte,
		heightByte,
		randomMatrixByte},
		[]byte(""))
	return data
}
